# AI Playground ***Copyright © Quectel Wireless Solutions Co., Ltd. 2026. All rights reserved.*** --- ## 简介 **AI Playground用户体验平台** 旨在为行业用户提供"开箱即用"的软硬一体化AI功能体验。无论是个人开发者还是企业创新团队,AI Playground都能提供全链路支持,让复杂的AI技术高效转化为可落地的实践路径,加速AI创新与产品化进程。 本节主要介绍 **AI Playground用户体验平台** 对 **Android端算法SDK的集成开发指南**,旨在帮助开发者快速了解与应用部署各类AI算法。 ## 集成的算法服务 | **算法类型** | **功能描述** | **应用场景** | | --- | --- | --- | | 人脸识别 | 人脸检测、关键点定位、特征提取与比对 | 门禁考勤、身份验证 | | 口罩检测 | 检测口罩佩戴状态(正确/错误/未佩戴) | 公共场所安全监测 | | 手势识别 | 识别常见手势动作 | 人机交互、智能家居 | | 二维码识别 | 快速扫描和解码二维码/条形码 | 支付、信息获取 | | OCR文字识别 | 图片和相机流中的文字提取 | 证件识别、票据处理 | ## 部署前准备 ### 开发环境要求 | **项目** | **要求** | | --- | --- | | IDE | Android Studio Arctic Fox (2020.3.1) 或更高版本 | | Android SDK | compileSdk 32 | | Android NDK | 21.4.7075529 | | CMake | 3.22.1 | | Gradle | 7.0+ | | JDK | 1.8 | | 目标设备 | ARM64架构(arm64-v8a) | | 最低系统版本 | Android 5.0 (API 21) | | 目标系统版本 | Android 11 (API 30) | **注:** 设备需先连接adb工具,执行 `adb root`,再执行 `adb shell setenforce 0` ### 必需软件安装 #### Android Studio配置 1. **安装NDK和CMake** 打开 Android Studio → Settings → Appearance & Behavior → System Settings → Android SDK - 切换到 **SDK Tools** 标签页 勾选以下组件并安装: - NDK (Side by side) → 选择版本 21.4.7075529 - CMake → 选择版本 3.22.1 - Android SDK Build-Tools - Android SDK Platform-Tools 2. **配置环境变量**(Windows系统) ```plaintext ANDROID_NDK_HOME = C:\Users\[用户名]\AppData\Local\Android\Sdk\ndk\21.4.7075529 ``` #### Gradle配置 项目根目录 build.gradle: ```groovy plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.kotlin.android) apply false } ``` ### 项目依赖库 #### Java/Kotlin依赖 #### Native依赖库(.so文件) 项目依赖以下预编译的Native库([官网联系我们获取]()),需放置在 `app/src/main/jniLibs/arm64-v8a/` 目录: | **库名称** | **说明** | | --- | --- | | libedge_computing_market.so | 主算法库(项目编译产物) | | libinference.so | 推理引擎 | | libopencv_java4.so | OpenCV图像处理库 | | libfacerecognition.so | 人脸识别算法库 | | libmaskdet.so | 口罩检测算法库 | | libpipeline.so | 手势识别Pipeline库 | | libdeepocr.so | OCR文字识别库 | | libzbar.so | 二维码识别库 | | libzbar_ex.so | 二维码扩展库 | | libjsoncpp.so | JSON解析库 | | libc++_shared.so | C++运行时库 | #### 模型文件部署 模型文件需部署到设备的指定目录,项目默认路径结构: ```plaintext /data/user/0/com.quectel.edgecomputingmarket/files/model/ ├── face # 子模块名 │ └── sim.engine # 子模块模型文件 ├── hand │ └── sim.engine ├── person │ └── sim.engine └── reid └── sim.engine ``` ## 模型核心信息 ### 模型算法列表 | **序号** | **算法名称** | **模型类型** | **输入尺寸** | **硬件加速** | **说明** | | --- | --- | --- | --- | --- | --- | | 1 | 人脸识别 | 识别模型 | 256×256 | DSP | 识别图片中的人脸 | | 2 | 口罩检测 | 检测模型 | 640×640 | DSP | 三分类:正确/错误/未佩戴 | | 3 | 手势检测 | 检测模型 | 640×640 | DSP | 检测手势区域 | | 4 | OCR文字识别 | 识别模型 | - | DSP | 文字内容识别 | | 5 | 二维码识别 | 解码算法 | - | CPU | 支持QR Code等多种码制 | ### 各算法详细说明 #### 人脸识别 **功能模块:** - **人脸检测**:定位图片/视频流中的人脸位置,输出边界框坐标 - **关键点定位**:提取5个面部关键点(左眼、右眼、鼻尖、左嘴角、右嘴角) - **特征提取**:生成128维人脸特征向量 - **人脸比对**:通过余弦相似度计算人脸相似性 **技术参数:** | **参数** | **数值** | | --- | --- | | 检测置信度阈值 | 0.5 | | NMS阈值 | 0.5 | | 最小人脸面积比例 | 1%(图像面积) | | 特征维度 | 128维 | | 相似度阈值 | 0.6 | #### 口罩检测 **检测类别:** | **类别ID** | **类别名称** | **说明** | | --- | --- | --- | | 0 | Correctly_Worn | 正确佩戴口罩 | | 1 | Not_Worn | 未佩戴口罩 | | 2 | Incorrectly_Worn | 错误佩戴口罩(仅遮口/仅遮鼻等) | **技术参数:** | **参数** | **默认值** | | --- | --- | | 置信度阈值 | 0.25 | | NMS阈值 | 0.45 | | 最小检测框面积 | 1%(图像面积) | #### 手势识别 **支持的手势类型:** | **序号** | **手势名称** | **说明** | | --- | --- | --- | | 1 | like | 点赞手势 | | 2 | dislike | 踩/倒赞手势 | | 3 | call | 打电话手势 | | 4 | fist | 握拳 | | 5 | four | 四指手势 | | 6 | mute | 静音手势 | | 7 | ok | OK手势 | | 8 | one | 食指手势 | | 9 | palm | 手掌张开 | | 10 | other | 其他手势 | | 11 | three | 三指手势 | | 12 | yeah | 胜利/V字手势 | **Pipeline流程:** 检测 → 裁剪 → 分类 #### OCR文字识别 **功能特性:** - 支持多行文本检测 - 自动识别中英文混合文本 - 返回文本内容和位置信息 **技术参数:** | **参数** | **数值** | | --- | --- | | 置信度阈值 | 0.2 | | 输入尺寸 | 256×256 | | 支持语言 | 中文、英文 | #### 二维码识别 **支持的码制:** - QR Code - EAN-13(可配置关闭) - 其他ZBar支持的码制 **配置参数:** ```cpp g_qrScanner->set_config(ZBAR_QRCODE, ZBAR_CFG_X_DENSITY, 4); g_qrScanner->set_config(ZBAR_QRCODE, ZBAR_CFG_Y_DENSITY, 4); ``` ## 模型移植流程(以人脸识别为例) 本小节以人脸识别为例,详细介绍如何从零开始在Android Studio中集成算法模型。 ### 项目结构创建 ```plaintext app/src/main/ ├── cpp/ │ ├── CMakeLists.txt # CMake配置文件 │ ├── edge_computing_market.cpp # JNI接口实现 │ └── include/ # 头文件目录 │ ├── faceRecogInterfaceEX.hpp │ ├── SNPEClass.hpp │ └── ... ├── java/com/quectel/edgecomputingmarket/ │ └── manager/ │ └── JniManager.java # JNI管理类 ├── jniLibs/arm64-v8a/ # Native库目录 │ ├── libSNPE.so │ ├── libopencv_java4.so │ └── ... └── assets/models/ └── face_model/ └── sim.engine ``` ### CMake配置 创建 app/src/main/cpp/CMakeLists.txt: ```cmake cmake_minimum_required(VERSION 3.22.1)project("edge_computing_market" LANGUAGES CXX)# ========== 路径配置 ==========set(THIRD_PARTY_SO_ROOT "${CMAKE_SOURCE_DIR}/../jniLibs")set(INCLUDE_ROOT "${CMAKE_SOURCE_DIR}/include")set(LOCAL_SRC_DIR "${CMAKE_SOURCE_DIR}")set(SUPPORTED_ABI "arm64-v8a")set(THIRD_PARTY_SO_DIR "${THIRD_PARTY_SO_ROOT}/${SUPPORTED_ABI}")# ========== 导入预编译库 ==========# OpenCV库set(OPENCV_LIB_NAME "opencv_java4")add_library(${OPENCV_LIB_NAME} SHARED IMPORTED)set_target_properties(${OPENCV_LIB_NAME} PROPERTIESIMPORTED_LOCATION "${THIRD_PARTY_SO_DIR}/lib${OPENCV_LIB_NAME}.so")# SNPE推理引擎set(SNPE_LIB_NAME "SNPE")add_library(${SNPE_LIB_NAME} SHARED IMPORTED)set_target_properties(${SNPE_LIB_NAME} PROPERTIESIMPORTED_LOCATION "${THIRD_PARTY_SO_DIR}/lib${SNPE_LIB_NAME}.so")# C++运行时set(C_SHARED_LIB_NAME "c++_shared")add_library(${C_SHARED_LIB_NAME} SHARED IMPORTED)set_target_properties(${C_SHARED_LIB_NAME} PROPERTIESIMPORTED_LOCATION "${THIRD_PARTY_SO_DIR}/lib${C_SHARED_LIB_NAME}.so")# 人脸识别库set(FACE_LIB_NAME "facerecognition")add_library(${FACE_LIB_NAME} SHARED IMPORTED)set_target_properties(${FACE_LIB_NAME} PROPERTIESIMPORTED_LOCATION "${THIRD_PARTY_SO_DIR}/lib${FACE_LIB_NAME}.so")# ========== 头文件配置 ==========function(recursive_include dir)if(IS_DIRECTORY ${dir})include_directories(${dir})file(GLOB SUB_DIRS RELATIVE ${dir} "${dir}/*")foreach(sub_dir ${SUB_DIRS})set(full_sub_dir "${dir}/${sub_dir}")if(IS_DIRECTORY ${full_sub_dir})recursive_include(${full_sub_dir})endif()endforeach()endif()endfunction()recursive_include(${INCLUDE_ROOT})# ========== 编译edge_computing_market.so ==========file(GLOB LOCAL_SRC_FILES "${LOCAL_SRC_DIR}/edge_computing_market.cpp")find_library(LOG_LIB log REQUIRED)find_library(JNI_GRAPHICS_LIB jnigraphics REQUIRED)add_library(edge_computing_market SHARED ${LOCAL_SRC_FILES})target_compile_features(edge_computing_market PRIVATE cxx_std_17)target_compile_options(edge_computing_market PRIVATE -frtti -fexceptions -Wno-error=format-security -fPIC )target_link_libraries(edge_computing_market PRIVATE${C_SHARED_LIB_NAME}${FACE_LIB_NAME}${OPENCV_LIB_NAME}${SNPE_LIB_NAME}${JNI_GRAPHICS_LIB}${LOG_LIB} c m dl atomic ) ``` ### JNI接口层实现 #### Java层接口定义 创建 JniManager.java: ```java package com.quectel.edgecomputingmarket.manager;public class JniManager {private static final String TAG = "JniManager";private static final String SO_NAME = "edge_computing_market";private static volatile JniManager INSTANCE;private static boolean isSoLoaded = false;// 加载Native库static {try {System.loadLibrary(SO_NAME); isSoLoaded = true;} catch (UnsatisfiedLinkError e) { isSoLoaded = false;}}// 单例模式public static JniManager getInstance() {if (INSTANCE == null) {synchronized (JniManager.class) {if (INSTANCE == null) { INSTANCE = new JniManager();}}}return INSTANCE;}// ========== 人脸识别接口 ==========/** * 设置SNPE环境变量 * @param soPath Native库路径 */public native void setAdspEnv(String soPath);/** * 初始化人脸识别模型 * @param faceDetModelPath 人脸检测模型路径 * @param faceLandmarkModelPath 关键点模型路径 * @param faceRegModelPath 特征提取模型路径 * @param faceDbPath 人脸库路径 * @return 0-成功,负数-错误码 */public native int initFaceRecognition(String faceDetModelPath,String faceLandmarkModelPath,String faceRegModelPath,String faceDbPath );/** * 从图片进行人脸识别 * @param imagePath 输入图片路径 * @param saveDir 结果图片保存目录 * @return 结果图片路径 */public native String faceRecognitionFromImage(String imagePath, String saveDir);/** * 从相机流进行实时人脸识别 * @param mergedYuvData YUV数据 * @param width 图像宽度 * @param height 图像高度 * @param rotation 旋转角度 * @return 检测结果数组 [人脸数, x, y, w, h, id, score, 关键点数, 关键点坐标...] */public native float[] faceRecognitionFromCameraX(byte[] mergedYuvData,int width,int height,int rotation );/** * 释放人脸识别资源 */public native void releaseCameraFaceRecog();} ``` #### C++层JNI实现 创建 edge_computing_market.cpp(核心代码片段): ```cpp #include #include #include #include #include "faceRecogInterfaceEX.hpp"#include "opencv2/opencv.hpp"#define LOG_TAG "JNI_edge_computing_market"#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)// 全局人脸识别实例static std::unique_ptr g_frIfEx = nullptr;static std::vector> g_faceFeatures;static int g_validFeatureDim = 0;void SetAdspLibraryPath(const std::string &nativeLibPath) { std::stringstream spath; std::stringstream spath1; spath << nativeLibPath << ";/vendor/lib/rfsa/adsp;/vendor/dsp;/vendor/dsp/cdsp"; spath1 << nativeLibPath;LOGD("nativeLibPath is %s", nativeLibPath.c_str());if (setenv("ADSP_LIBRARY_PATH", spath.str().c_str(), 1 /*override*/) == 0) {LOGD("SNPE环境配置成功");} else {LOGD("SNPE环境配置失败");}if (setenv("LD_LIBRARY_PATH", spath1.str().c_str(), 1 /*override*/) == 0) {LOGD("SNPE环境配置成功");} else {LOGD("SNPE环境配置失败");}LOGD("ADSP_LIBRARY_PATH is %s", getenv("ADSP_LIBRARY_PATH"));LOGD("LD_LIBRARY_PATH is %s", getenv("LD_LIBRARY_PATH"));}extern "C" JNIEXPORT void JNICALL Java_com_quectel_edgecomputingmarket_manager_JniManager_setAdspEnv( JNIEnv *env, jobject thiz, jstring soLibraryPath ) {const char *ldPath = env->GetStringUTFChars(soLibraryPath, nullptr);if (ldPath == nullptr) {LOGE("环境变量路径为空!");goto end;}SetAdspLibraryPath(ldPath); end: env->ReleaseStringUTFChars(soLibraryPath, ldPath);}/** * 初始化人脸识别模型 */extern "C" JNIEXPORT jint JNICALL Java_com_quectel_edgecomputingmarket_manager_JniManager_initFaceRecognition( JNIEnv *env, jobject thiz, jstring face_det_model_path, jstring face_landmark_model_path, jstring face_reg_model_path, jstring face_db_path) {try {if (g_frIfEx != nullptr) {LOGD("人脸识别已初始化");return 0;}// 获取模型路径char *detModel = const_cast(env->GetStringUTFChars(face_det_model_path, nullptr));char *landmarkModel = const_cast(env->GetStringUTFChars(face_landmark_model_path, nullptr));char *regModel = const_cast(env->GetStringUTFChars(face_reg_model_path, nullptr));char *dbPath = const_cast(env->GetStringUTFChars(face_db_path, nullptr));// 创建人脸识别实例 g_frIfEx = std::make_unique( detModel, landmarkModel, regModel);int err = g_frIfEx->init();if (err != 0) { g_frIfEx.reset();return err;}// 加载人脸库 std::vector filePaths;glob(dbPath, filePaths); g_faceFeatures.clear();for (const auto &path: filePaths) { cv::Mat img = cv::imread(path);if (img.empty()) continue; std::vector faceResults; err = g_frIfEx->detect_face(img, faceResults, 0.5, 0.5);if (err == 1 && !faceResults.empty() && !faceResults[0].feature.empty()) { g_faceFeatures.emplace_back(faceResults[0].feature); g_validFeatureDim = faceResults[0].feature.size();}}// 释放字符串资源 env->ReleaseStringUTFChars(face_det_model_path, detModel); env->ReleaseStringUTFChars(face_landmark_model_path, landmarkModel); env->ReleaseStringUTFChars(face_reg_model_path, regModel); env->ReleaseStringUTFChars(face_db_path, dbPath);return g_faceFeatures.empty() ? -2 : 0;} catch (...) {return -5;}}/** * 相机流人脸识别 */extern "C" JNIEXPORT jfloatArray JNICALL Java_com_quectel_edgecomputingmarket_manager_JniManager_faceRecognitionFromCameraX( JNIEnv *env, jobject thiz, jbyteArray mergedYuvData_, jint width, jint height, jint rotation) {if (g_frIfEx == nullptr || g_faceFeatures.empty()) { jfloatArray emptyArray = env->NewFloatArray(1);float emptyVal = 0.0f; env->SetFloatArrayRegion(emptyArray, 0, 1, &emptyVal);return emptyArray;}// 获取YUV数据 jbyte *mergedYuvData = env->GetByteArrayElements(mergedYuvData_, nullptr);// YUV转BGR cv::Mat yuvMat(height * 3 / 2, width, CV_8UC1, (unsigned char *) mergedYuvData); cv::Mat bgrMat; cv::cvtColor(yuvMat, bgrMat, cv::COLOR_YUV2BGR_I420);// 图像旋转 cv::Mat rotatedMat;switch (rotation) {case 90: cv::rotate(bgrMat, rotatedMat, cv::ROTATE_90_CLOCKWISE); break;case 180: cv::rotate(bgrMat, rotatedMat, cv::ROTATE_180); break;case 270: cv::rotate(bgrMat, rotatedMat, cv::ROTATE_90_COUNTERCLOCKWISE); break;default: rotatedMat = bgrMat.clone(); break;}// 人脸检测 std::vector faceResults; g_frIfEx->detect_face(rotatedMat, faceResults, 0.5f, 0.5f);// 特征匹配for (auto &face: faceResults) {if (!face.feature.empty() && face.feature.size() == g_validFeatureDim) { std::pair matchRes = g_frIfEx->cosine_similarity( g_faceFeatures, face.feature, 0.6f); face.code = matchRes.first;}}// 构建返回结果int resultSize = 1;for (const auto &face: faceResults) { resultSize += 7 + face.key_pts.size() * 2;} jfloatArray resultArray = env->NewFloatArray(resultSize); std::unique_ptr resultData(new float[resultSize]);int idx = 0; resultData[idx++] = static_cast(faceResults.size());for (const auto &face: faceResults) { resultData[idx++] = static_cast(face.x); resultData[idx++] = static_cast(face.y); resultData[idx++] = static_cast(face.width); resultData[idx++] = static_cast(face.height); resultData[idx++] = static_cast(face.code); resultData[idx++] = 0.0f; // score resultData[idx++] = static_cast(face.key_pts.size());for (const auto &pt: face.key_pts) { resultData[idx++] = static_cast(pt.x); resultData[idx++] = static_cast(pt.y);}} env->SetFloatArrayRegion(resultArray, 0, resultSize, resultData.get()); env->ReleaseByteArrayElements(mergedYuvData_, mergedYuvData, JNI_ABORT);return resultArray;}/** * 释放资源 */extern "C" JNIEXPORT void JNICALL Java_com_quectel_edgecomputingmarket_manager_JniManager_releaseCameraFaceRecog( JNIEnv *env, jobject thiz) { g_frIfEx.reset(); g_faceFeatures.clear(); g_validFeatureDim = 0;} ``` #### build.gradle配置 在 app/build.gradle 中添加: ```groovy android { // ... 其他配置 ... defaultConfig { // ... 其他配置 ... externalNativeBuild { cmake { cppFlags "" abiFilters 'arm64-v8a' } } ndk { abiFilters 'arm64-v8a' } } externalNativeBuild { cmake { path "src/main/cpp/CMakeLists.txt" version "3.22.1" } } ndkVersion '21.4.7075529' } ``` ### 使用示例 #### 初始化模型 ```java public class MainActivity extends AppCompatActivity {private JniManager jniManager;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 获取JNI管理器实例 jniManager = JniManager.getInstance();// 设置SNPE环境String nativeLibDir = getApplicationInfo().nativeLibraryDir; jniManager.setAdspEnv(nativeLibDir);// 初始化人脸识别String modelPath = getFilesDir() + "/model/face_model";String faceDbPath = getFilesDir() + "/model/facedb";int result = jniManager.initFaceRecognition(//参数传模型文件的路径);if (result == 0) {Log.d("EdgeComputingMarket", "人脸识别初始化成功");} else {Log.e("EdgeComputingMarket", "人脸识别初始化失败: " + result);}}} ``` #### 相机流人脸识别 以CameraX为例,需要导入相关依赖,也可自行选择别的相机实现方式: ```java // 使用CameraX获取预览帧 previewView.getPreviewStreamState().observe(this, state -> {// 相机准备就绪});// 在ImageAnalysis中处理帧数据ImageAnalysis imageAnalysis = new ImageAnalysis.Builder().setTargetResolution(new Size(640, 480)).setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST).build(); imageAnalysis.setAnalyzer(executor, image -> {// 获取YUV数据ImageProxy imageProxy = image;byte[] yuvData = yuv420ToNv21(imageProxy);// 调用人脸识别float[] results = jniManager.faceRecognitionFromCameraX( yuvData, imageProxy.getWidth(), imageProxy.getHeight(),getRotationDegrees());// 解析结果if (results != null && results.length > 1) {int faceCount = (int) results[0];int idx = 1;for (int i = 0; i < faceCount; i++) {float x = results[idx++];float y = results[idx++];float w = results[idx++];float h = results[idx++];int id = (int) results[idx++];// 跳过score和关键点 idx += 2 + (int) results[idx - 1] * 2;Log.d("FaceResult", "Face " + i + ": pos=(" + x + "," + y +"), id=" + id);}} image.close();}); ``` #### 资源释放 ```java @Overrideprotected void onDestroy() {super.onDestroy();if (jniManager != null) { jniManager.releaseCameraFaceRecog();}} ``` ## Demo演示 左侧tab栏选择想要体验的功能,Local Picture是选择本地图片识别,Live Camera是调用设备摄像头识别,APM是查看设备性能变化以及运行日志。 选择Local Picture,点击Before区域的灰白区域即可上传图片。 选择Live Camera,点击开始拍照,即可开启摄像头识别。
android_aimarket_demo
## 常见问题排查 **注意:** 本项目需要使用到系统库,需确保targetSdk ≤ 30,在安装应用之前需要先adb连接设备执行 `adb root` 命令,然后执行 `adb shell setenforce 0` 命令,且需要在算法的so库初始化之前设置adsp环境变量,才能正常使用,参考上面的代码。 | **问题** | **可能原因** | **解决方案** | | --- | --- | --- | | 算法时效性失效 | 算法初始化后会有一个小时的时效性,超时会失效 | 退出当前应用并清理后台重新启动 | | UnsatisfiedLinkError | .so库未正确加载 | 检查jniLibs目录结构和架构是否匹配以及环境变量是否设置 | | 模型初始化失败(-1) | 模型文件不存在或路径错误 | 检查模型文件是否正确复制到设备 | | 人脸库加载失败(-2) | 人脸库图片格式不正确 | 确保使用标准JPEG格式图片 | | 检测结果为空 | 图像格式或尺寸不正确,有些相机的角度不固定,导致拍摄出来的画面出现旋转镜像等问题 | 检查YUV数据格式和旋转角度,对传入的图片进行正确旋转 | | DSP不可用 | 设备不支持SNPE DSP | 更换设备 | ## 更多算法,尽在AlgoStore算法超市 本文档当前仅提供部分算法的集成开发指南,如需获取更丰富、更全面的 AI 算法能力,欢迎前往 AlgoStore 算法超市,海量优质算法随心选用,满足多样化开发与落地需求。 ## AlgoStore算法超市 ### AlgoStore算法超市简介 AlgoStore算法超市是由移远通信(Quectel)开发的一站式AI算法服务平台。作为一站式AIoT算法平台,AlgoStore就像一个汇聚丰富前沿AI算法的"超级市场",凭借强大的算法适配能力、便捷的算法定制服务和完善的AIoT生态体系,让企业和开发者能够像"选购商品"般轻松获取合适的AI算法,助力其破解业务难题的同时,驱动产品加速创新。 [AlgoStore算法超市入口]() ### 核心特性 - **算法领先**:汇聚了包括视觉、音频等多领域的先进AI算法,以上的核心算法经客户实际项目验证,并且不断迭代创新。 - **硬件适配**:算法全面覆盖Qualcomm、Unisoc、Rockchip、MediaTek等主流芯片,消除选型顾虑。 - **深度定制**:全球化布局与客户服务网络,针对差异化业务需求提供深度定制化服务,打造专属解决方案。 ### 联系我们 若想要获取更多算法体验或者获取算法集成SDK,请前往官网联系我们! ```{image} images/image_NqagbJjZko4kIqxBuEzcParkn2f.webp :width: 1908px :height: 992px ``` ## AIDE开发工具链 ### AIDE开发工具链简介 移远通信一站式 AI 开发工具链 AIDE(AI Development Tools & Engines)是面向AI应用开发者和端侧设备厂商的一站式开发工具链平台,核心覆盖"模型转换 - 优化- 端侧推理"全链路,旨在解决 **多模型格式适配难**、**多硬件平台兼容复杂**、**端侧推理性能不足** 三大核心痛点,加速 AI 模型从训练完成到业务落地的周期。 [AIDE开发工具链入口]() ### 核心模块 - **ModelForge AI模型转换与优化工具**:支持多种主流模型格式上传与多芯片平台。通过通道量化、位宽量化、模型校准等多元量化优化技术,实现模型轻量化压缩与推理效率跃升,打造更适配各类智能终端场景的端侧部署模型,确保转换后模型精度与性能双达标。 - **ModelInfer AI推理引擎**:深度适配CPU/GPU/NPU异构计算单元,支持多计算单元协同推理,显著降低延迟、提升硬件资源利用率,缩短从模型算法研发到应用落地的周期。 ### 模型转换与部署 本节将通过示例演示将 mobilenet v3 的模型转化到端侧部署全过程。 #### 模型转换 **模型上传** 登录AI开放平台,选择AI开发工具链。在源模型界面选择合适的模型格式,然后点击同意隐私协议和使用条款。上传模型并等待完成。 ```{image} images/image_XE31bdtvvox5YCxlV3ocZGJ9nhg.webp :width: 865px :height: 573px ``` **选择目标硬件** 在目标硬件平台依次选择合适的目标硬件平台,芯片平台以及推理引擎。 ```{image} images/image_H6AobK81Xoq5pcxvAgdcDcmLnFb.webp :width: 866px :height: 608px ``` **模型配置** 在模型配置界面可以设置模型名称,输入尺寸会根据上传模型自动获取,也可根据实际需求设置。 ```{image} images/image_Dhf9b5U71oRpcmxdYj2c8CionHb.webp :width: 865px :height: 649px ``` 模型量化可以减少模型大小并提升推理速度,但是会降低准确率。开启模型量化后,会出现如下量化参数界面。量化方法中可以选择是否启用通道优化,以进一步减小模型大小。接下来可以选择量化位宽,根据实际模型去设置输入的均值和方法。最后选择校准数据集,默认使用COCO数据集,也可以选择自定义数据集然后上传自己的校准数据集。 ```{image} images/image_VNIebaJCzon2MdxYHCWcrZdLnWf.webp :width: 865px :height: 558px ``` **确认配置** 接下来将会展示模型转换的信息,确认无误就可以进行模型转换,如果有修改可以点击上一步进行修改。 ```{image} images/image_E0HtbyvxQoPCUUxD3uXciudPnOb.webp :width: 864px :height: 597px ``` **模型转换** 模型转换成功后,可以点击按钮即可下载模型。 ```{image} images/image_BFR1bMceXockWrxo2v8c6YkJnXg.webp :width: 864px :height: 508px ``` ### 部署示例 **cmake集成** 由于ModelInfer SDK已经配置了集成的config.cmake,方便我们进行集成。我们只需要CMakeLists.txt文件中设置ModelInfer SDK的路径,通过find_package查找推理引擎SDK包,包含头文件,定义以及链接库文件即可以完成cmake集成,如下所示: ```cmake cmake_minimum_required(VERSION 3.1)project(MODEL_INFER_TEST)set(CMAKE_CXX_STANDARD 14)set(OpenCV_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/OpenCV-android-sdk/sdk/native/jni")find_package(OpenCV REQUIRED)find_library(log-lib log )set(ModelInfer_DIR ${CMAKE_CURRENT_SOURCE_DIR}/model_infer_sdk/0.0.1/Android_aarch64_Release_Q)find_package(ModelInfer REQUIRED)include_directories(${ModelInfer_INCLUDE_DIRS})add_definitions(${ModelInfer_DEFINITIONS})include_directories(${OpenCV_INCLUDE_DIRS})add_executable(${PROJECT_NAME} main.cc)target_link_libraries(${PROJECT_NAME}${ModelInfer_LIBRARIES}${OpenCV_LIBRARIES} log ) ``` **SDK使用示例** 本例以mobilenet分类算法部署的详细说明使用。 包含头文件: ```cpp #include "model_inference.hpp" ``` 具体使用代码如下: ```cpp void test_qnn_sdk(const std::string& model_path, const std::string& image_path) {#ifdef HAVE_INFERENCE_QNN std::unique_ptr qnnDelegate = std::make_unique(); std::string auth_code = "your_auth_code"; // 替换为实际auth codeint module = DEV_SG560D; // 替换为实际硬件,见:ql_slas.h,QuecSlasDev_eif (0 != qnnDelegate->Authenticate(module, auth_code)) { std::cout << "fail to authenticate\n";return;}if (0 != qnnDelegate->Init(model_path.c_str())) { std::cout << "fail to init\n";return;} else { std::cout << "init success\n";} std::vector input_arr(224*224*3); // 假设输入大小为3x224x224 std::vector out_arr(1000);PreProcess(image_path, input_arr); std::vector input_buffers; input_buffers.emplace_back(input_arr.data()); std::vector output_buffers; output_buffers.emplace_back(out_arr.data()); qnnDelegate->Inference(input_buffers, output_buffers);PostProcess(out_arr);#endif} ``` 首先需要声明推理引擎,本例中使用的是QnnEngine,通过认证函数Authenticate后方可以使用推理引擎,通过调用Init函数来进行初始化,需要注意的是通过模型转换平台下载的模型均已经加密,所以Init第二个参数需要设置true。 然后准备模型输入输出参数,ModelInfer引擎均支持多头输入输出。需要注意的是推理引擎并不会创建以及管理内存,所有的输入输出buffer的中内存的创建,管理以及释放需要由使用者进行操作。最后通过调用Inference函数既可以完成一次推理操作。PreProcess和PostProcess分别为mobilenet的前后处理过程。其具体实现如下: ```cpp void PreProcess(const std::string& image_path, std::vector& input_arr) { cv::Mat image = cv::imread(image_path, -1); cv::cvtColor(image, image, cv::COLOR_BGR2RGB);int h = image.rows;int w = image.cols;int target_size = 256; // 计算缩放比例float scale = target_size / (float)std::min(h, w);int new_h = static_cast(h * scale);int new_w = static_cast(w * scale); cv::Mat resized; cv::resize(image, resized, cv::Size(new_w, new_h), 0, 0, cv::INTER_LINEAR); // 中心裁剪到224x224int crop_size = 224;int start_y = (resized.rows - crop_size) / 2;int start_x = (resized.cols - crop_size) / 2; cv::Rect roi(start_x, start_y, crop_size, crop_size); resized = resized(roi).clone(); cv::Mat float_img; resized.convertTo(float_img, CV_32FC3, 1.0 / 255.0); std::vector mean = {0.485, 0.456, 0.406}; // R, G, B std::vector std = {0.229, 0.224, 0.225}; std::vector channels(3); cv::split(float_img, channels);for (int i = 0; i < 3; i++) { channels[i] = (channels[i] - mean[i]) / std[i];} cv::Mat normalized; cv::merge(channels, normalized);float *normalized_ptr = reinterpret_cast(normalized.data); std::copy(normalized_ptr, normalized_ptr + input_arr.size(), input_arr.begin());}void PostProcess(std::vector& out_arr) {auto probs = Softmax(out_arr.data(), out_arr.size());float sum = 0.0f;for (const auto& p : probs) { sum += p;} std::vector top5_idx = ArgSort(probs, 5); std::vector> results;for (size_t idx : top5_idx) { results.emplace_back(idx, probs[idx]);} std::vector class_names; std::ifstream ifs("./imagenet_classes.txt");if (!ifs.is_open()) { std::cerr << "Failed to open class names file.\n";return;} std::string line;while (std::getline(ifs, line)) { class_names.push_back(line);} std::cout << "Top-5 Predictions:\n";for (const auto& res : results) { std::cout << "Class ID: " << res.first << ", Probability: " << res.second << ", Class Name: " << class_names[res.first] << "\n";}} ``` 其中Softmax和ArgSort函数如下: ```cpp std::vector Softmax(const float* data, size_t size) { std::vector result(size);float max_val = *std::max_element(data, data + size);float sum = 0.0f; // For numerical stabilityfor (size_t i = 0; i < size; i++) { result[i] = std::exp(data[i] - max_val); sum += result[i];} // Normalizefor (size_t i = 0; i < size; i++) { result[i] /= sum;}return result;} ``` ```cpp std::vector ArgSort(const std::vector& probs, int k) {// Create index array std::vector indices(probs.size()); std::iota(indices.begin(), indices.end(), 0); // Partial sort for top-k std::partial_sort(indices.begin(), indices.begin() + k, indices.end(), [&probs](size_t i1, size_t i2) {return probs[i1] > probs[i2];}); // Return only top-k indicesreturn std::vector(indices.begin(), indices.begin() + k);} ``` **编译运行** 通过cmake编译出可以执行程序,push到终端设备上进行运行,可以获得结果。 ```bash adb shell "cd /data/local/tmp/model_infer_demo/ && chmod +x ./MODEL_INFER_TEST && export LD_LIBRARY_PATH=/data/local/tmp/model_infer_demo/:$LD_LIBRARY_PATH && ./MODEL_INFER_TEST ./mobilenetv3_ctx.bin ./20260204-101425.jpg" init success Top-5 Predictions: Class ID: 340, Probability: 0.0700765, Class Name: zebra Class ID: 396, Probability: 0.0140236, Class Name: lionfish Class ID: 117, Probability: 0.0109065, Class Name: chambered nautilus Class ID: 506, Probability: 0.0106358, Class Name: coil Class ID: 56, Probability: 0.00643312, Class Name: king snake ```